using FireStarter.Base;
using Firestarter.DB.Schema.BO;
using Firestarter.DB.Schema.Logic;
using Firestarter.DB.Schema.Test.BO.TestUtils;
using Firestarter.DB.Schema.Test.Logic.DBMerge;
using FireStarterModeller.Test;
using Habanero.BO;
using Habanero.BO.ClassDefinition;
using Habanero.BO.Loaders;
using Habanero.Testability;
using NUnit.Framework;

namespace Firestarter.DB.Schema.Test.Logic
{
    [TestFixture]
    public class TestMergeDBTable
    {
        [TestFixtureSetUp]
        public virtual void TestFixtureSetup()
        {
            ClassDef.ClassDefs.Clear();
            string xmlClassDefs = BOBroker.GetClassDefsXml();
            ClassDef.ClassDefs.Add(new XmlClassDefsLoader(xmlClassDefs, new DtdLoader()).LoadClassDefs());
            BORegistry.DataAccessor = new DataAccessorInMemory();
        }

        [SetUp]
        public virtual void SetupTest()
        {
        }

        [TestCase("DateCreated", true)]
        [TestCase("Owner", true)]
        [TestCase("TableNameDB", true)]
        [TestCase("TableType", false)]
        [TestCase("TableComment", true)]
        [TestCase("CreateTime", true)]
        [TestCase("UpdateTime", true)]
        [TestCase("TableID", false)]
        [TestCase("Approved", false)]
        [TestCase("DatabaseID", false)]
        [TestCase("PendingChangeType", false)]
        public void Test_MustMergeProp_ShouldBeSetForProp(string propName, bool expectedMustMergeProp)
        {
            //---------------Set up test pack-------------------
            DBMergeBO<DBTable> merger = GetDBMergeDBTable();
            var dbTable = new DBTable();
            //---------------Assert Precondition----------------
            //---------------Execute Test ----------------------
            var boProp = dbTable.Props[propName];
            var returnedMustMergeProp = merger.MustMergeProperty(boProp);
            //---------------Test Result -----------------------
            Assert.AreEqual(expectedMustMergeProp, returnedMustMergeProp, "Prop '" + propName + "' should be MustMerge = '" + expectedMustMergeProp +"'");
        }

        private DBMergeBO<DBTable> GetDBMergeDBTable()
        {
            return new DBMergeDBTable();
        }

        [TestCase("Columns", true)]
        [TestCase("ForeignKeys", true)]
        [TestCase("Keys", true)]
        [TestCase("PrimaryKeyDB", true)]
        [TestCase("Database", false)]
        [TestCase("MappedDMClass", false)]
        [TestCase("RelatedForeignKeys", false)]
        public void Test_MustMergeRelationship_ShouleBeSetForRelationships(string relationshipName, bool expectedMustMergeRel)
        {
            //---------------Set up test pack-------------------
            DBMergeBO<DBTable> merger = GetDBMergeDBTable();
            var dbTable = new DBTable();
            //---------------Assert Precondition----------------

            //---------------Execute Test ----------------------

            var relationship = dbTable.Relationships[relationshipName];
            var returnedMustMergeProp = merger.MustMergeRelationship(relationship);
            //---------------Test Result -----------------------
            Assert.AreEqual(expectedMustMergeRel, returnedMustMergeProp, "Relationship '" + relationship.RelationshipName + "' should be MustMerge = '" + expectedMustMergeRel + "'");
        }

        #region Merge Keys (PK, FK and AK)

        /// <summary>
        /// The <see cref="DBTable.Columns"/> must be merged first so that the 
        /// <see cref="DBTable.PrimaryKeyDB"/>.<see cref="DBPrimaryKey.PrimaryKeyColumns"/>
        /// <see cref="DBTable.ForeignKeys"/>.<see cref="DBForeignKey.ForeignKeyColumns"/>
        /// <see cref="DBTable.Keys"/>.<see cref="DBKey.KeyColumns"/>
        /// can be linked to the correct DBColumn for the table.
        /// </summary>
        [Test]
        public void Test_Merge_ShouldMergePKColumns()
        {
            //---------------Set up test pack-------------------
            DBMergeBO<DBTable> merger = GetDBMergeDBTable();

            DBTable fromTable = CreateTableWithPK();
            var toTable = new DBTable();
            //---------------Assert Precondition----------------
            Assert.Greater(fromTable.Columns.Count, 2);
            Assert.IsNotNull(fromTable.PrimaryKeyDB);
            Assert.AreEqual(1, fromTable.PrimaryKeyDB.PrimaryKeyColumns.Count);

            Assert.AreEqual(0, toTable.Columns.Count);
            Assert.IsNull(toTable.PrimaryKeyDB);
            //---------------Execute Test ----------------------
            toTable = merger.MergeBOFrom(fromTable, toTable);
            //---------------Test Result -----------------------
            Assert.AreEqual(fromTable.Columns.Count, toTable.Columns.Count);
            Assert.IsNotNull(toTable.PrimaryKeyDB);
            Assert.AreEqual(1, toTable.PrimaryKeyDB.PrimaryKeyColumns.Count);
            var toPKColumn = toTable.PrimaryKeyDB.PrimaryKeyColumns[0];
            Assert.IsNotNull(toPKColumn.Column, "PrimaryKeyColumn.Column should have been linked to the tables Column");
            var expectedToColumn = toTable.Columns.Find(dbcolumn => dbcolumn.ColumnName == toPKColumn.ColumnName);
            Assert.AreSame(expectedToColumn, toPKColumn.Column, "The column on the table should be the same one referenced by the PrimaryKeyColumn");
        }
        [Test]
        public void Test_Merge_ShouldMergeKeyColumns()
        {
            //---------------Set up test pack-------------------
            DBMergeBO<DBTable> merger = GetDBMergeDBTable();

            var fromTable = CreateTableWithKey();

            var toTable = new DBTable();
            //---------------Assert Precondition----------------
            Assert.Greater(fromTable.Columns.Count, 2);
            Assert.AreEqual(1, fromTable.Keys.Count);
            var fromKey = fromTable.Keys[0];
            Assert.AreEqual(1, fromKey.KeyColumns.Count);

            Assert.AreEqual(0, toTable.Columns.Count);
            Assert.IsNull(toTable.PrimaryKeyDB);
            //---------------Execute Test ----------------------
            toTable = merger.MergeBOFrom(fromTable, toTable);
            //---------------Test Result -----------------------
            Assert.AreEqual(fromTable.Columns.Count, toTable.Columns.Count);
            Assert.AreEqual(1, toTable.Keys.Count);
            var toKey = toTable.Keys[0];
            Assert.AreEqual(1, toKey.KeyColumns.Count);
            var toKeyColumn = toKey.KeyColumns[0];
            Assert.IsNotNull(toKeyColumn.Column, "KeyColumn.Column should have been linked to the tables Column");
            var expectedToColumn = toTable.Columns.Find(dbcolumn => dbcolumn.ColumnName == toKeyColumn.ColumnName);
            Assert.AreSame(expectedToColumn, toKeyColumn.Column, "The column on the table should be the same one referenced by the KeyColumn");
        }
        
        [Test]
        public void Test_Merge_ShouldMergeForeignKeyColumns()
        {
            //---------------Set up test pack-------------------
            DBMergeBO<DBTable> merger = GetDBMergeDBTable();

            var fromTable = CreateTableWithForeignKey();

            var toTable = new DBTable();
            //---------------Assert Precondition----------------
            Assert.Greater(fromTable.Columns.Count, 2);
            Assert.AreEqual(1, fromTable.ForeignKeys.Count);
            var fromKey = fromTable.ForeignKeys[0];
            Assert.AreEqual(1, fromKey.ForeignKeyColumns.Count);

            Assert.AreEqual(0, toTable.Columns.Count);
            Assert.AreEqual(0, toTable.ForeignKeys.Count);
            //---------------Execute Test ----------------------
            toTable = merger.MergeBOFrom(fromTable, toTable);
            //---------------Test Result -----------------------
            Assert.AreEqual(fromTable.Columns.Count, toTable.Columns.Count);
            Assert.AreEqual(1, toTable.ForeignKeys.Count);
            var toKey = toTable.ForeignKeys[0];
            Assert.AreEqual(1, toKey.ForeignKeyColumns.Count);
            var toKeyColumn = toKey.ForeignKeyColumns[0];
            Assert.IsNotNull(toKeyColumn.ParentColumn, "KeyColumn.Column should have been linked to the tables Column");
            var expectedToColumn = toTable.Columns.Find(dbcolumn => dbcolumn.ColumnName == toKeyColumn.ParentColumnName);
            Assert.AreSame(expectedToColumn, toKeyColumn.ParentColumn, "The column on the table should be the same one referenced by the KeyColumn");
        }


        [Test]
        public void Test_Merge_ShouldMergeForeignKeyColumnsAndRelatedColumns()
        {
            //---------------Set up test pack-------------------
            DBMergeBO<DBTable> merger = GetDBMergeDBTable();

            var fromParentTable = CreateTableWithForeignKey();
            var fromKey = fromParentTable.ForeignKeys[0];

            var toParentTable = new DBTable();

            var relatedColumnName = fromKey.ForeignKeyColumns[0].RelatedColumnName;
            DBColumn expectedToRelatedColumn;
            DBTable toRelatedTable = CreateRelatedTable(out expectedToRelatedColumn, relatedColumnName);

            var expectedToFK = new DBForeignKey { ParentTable = toParentTable, RelatedTable = toRelatedTable, ForeignKeyName = fromKey.ForeignKeyName };

            //---------------Assert Precondition----------------
            Assert.Greater(fromParentTable.Columns.Count, 2);
            Assert.AreEqual(1, fromParentTable.ForeignKeys.Count);

            Assert.AreEqual(1, fromKey.ForeignKeyColumns.Count);

            Assert.AreEqual(0, toParentTable.Columns.Count);
            Assert.AreEqual(1, toParentTable.ForeignKeys.Count);

            Assert.AreEqual(1, toRelatedTable.Columns.Count);
            Assert.AreEqual(relatedColumnName, toRelatedTable.Columns[0].ColumnName);

            //---------------Execute Test ----------------------
            toParentTable = merger.MergeBOFrom(fromParentTable, toParentTable);
            //---------------Test Result -----------------------
            Assert.AreEqual(fromParentTable.Columns.Count, toParentTable.Columns.Count);
            Assert.AreEqual(1, toParentTable.ForeignKeys.Count);

            var toKey = toParentTable.ForeignKeys[0];
            Assert.AreSame(expectedToFK, toKey);
            Assert.AreEqual(1, toKey.ForeignKeyColumns.Count);
            var toKeyColumn = toKey.ForeignKeyColumns[0];
            Assert.IsNotNull(toKeyColumn.ParentColumn, "KeyColumn.Column should have been linked to the tables Column");
            var expectedToColumn = toParentTable.Columns.Find(dbcolumn => dbcolumn.ColumnName == toKeyColumn.ParentColumnName);
            Assert.AreSame(expectedToColumn, toKeyColumn.ParentColumn, "The column on the table should be the same one referenced by the KeyColumn");

            Assert.AreSame(expectedToRelatedColumn, toKeyColumn.RelatedColumn);
        }

        private DBTable CreateRelatedTable(out DBColumn expectedToRelatedColumn, string relatedColumnName)
        {
            var toRelatedTable = new DBTable();
            expectedToRelatedColumn = toRelatedTable.Columns.CreateBusinessObject();
            expectedToRelatedColumn.ColumnName = relatedColumnName;
            return toRelatedTable;
        }

        private DBTable CreateTableWithForeignKey()
        {
            var dbForeignKeyColumn = CreateFKColumnWithFK();
            return dbForeignKeyColumn.ForeignKey.ParentTable;
        }

        private static string GetRandomString()
        {
            return RandomValueGen.GetRandomString();
        }

        private static DBForeignKeyColumn CreateFKColumnWithFK()
        {
            return CreateFKColumnWithFK(GetRandomString(), GetRandomString(), GetRandomString());
        }
        private static DBForeignKeyColumn CreateFKColumnWithFK(string fkName, string parentColumnName, string relatedColumnName)
        {
            var parentTable = TestUtilsDBTable.GetTestFactory()
                .WithMany(table => table.Columns)
                .CreateValidBusinessObject();
            var relatedTable = TestUtilsDBTable.GetTestFactory().CreateValidBusinessObject();

            var foreignKey = TestUtilsDBForeignKey.GetTestFactory()
                .WithValue(relationship => relationship.ForeignKeyName, fkName)
                .WithValue(relationship => relationship.ParentTable, parentTable)
                .WithValue(relationship => relationship.RelatedTable, relatedTable)
                .CreateValidBusinessObject();

            var parentColumn = TestUtilsDBColumn.GetTestFactory()
                .WithValue(column => column.ColumnName, parentColumnName)
                .WithValue(column => column.Table, foreignKey.ParentTable)
                .CreateValidBusinessObject();

            DBColumn relatedColumn = null;
            if (!string.IsNullOrEmpty(relatedColumnName))
            {
                relatedColumn = TestUtilsDBColumn.GetTestFactory()
                    .WithValue(column => column.ColumnName, relatedColumnName)
                    .WithValue(column => column.Table, foreignKey.RelatedTable)
                    .CreateValidBusinessObject();
            }

            var fkColumn = TestUtilsDBForeignKeyColumn.GetTestFactory()
                .WithValue(property => property.ForeignKey, foreignKey)
                .WithValue(property => property.RelatedColumn, relatedColumn)
                .WithValue(property => property.ParentColumn, parentColumn)
                .CreateValidBusinessObject();
            return fkColumn;
        }

        private DBTable CreateTableWithKey()
        {
            var fromTable = TestUtilsDBTable.GetTestFactory()
                .WithMany(table => table.Columns)
                .CreateValidBusinessObject();
            var fromKey = TestUtilsDBKey.GetTestFactory()
                .WithOne(key1 => key1.KeyColumns)
                .WithValue(key1 => key1.Table, fromTable)
                .CreateValidBusinessObject();

            DBKeyColumn keyColumn = fromKey.KeyColumns[0];
            keyColumn.Column = fromTable.Columns[1];
            return fromTable;
        }

        #endregion


        private static DBTable CreateTableWithPK()
        {
            var pk = TestUtilsDBPrimaryKey.GetTestFactory()
                .WithOne(key => key.PrimaryKeyColumns)
                .CreateValidBusinessObject();

            var fromTable = TestUtilsDBTable.GetTestFactory()
                .WithMany(table => table.Columns)
                .WithValue(dbTable => dbTable.PrimaryKeyDB, pk)
                .CreateValidBusinessObject();

            DBPrimaryKeyColumn pkColumn = pk.PrimaryKeyColumns[0];
            pkColumn.Column = fromTable.Columns[1];
            return fromTable;
        }
    }

}